/*
 * This file is part of quickReminder, a software for gamers
 *
 * Copyright (C) 2006-2008 Lucas Romero
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>.
 */
using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Management;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;

using LucasCode.PublicTypes;
using LucasCode.Tools;
using LucasCode.Win32;
using UserNotifier;
using SendKeys = System.Windows.Forms.SendKeys;
using Timer = System.Threading.Timer;
using System.Runtime.InteropServices;
using System.Globalization;
using ESLReminder.Modules;
using ESLReminder;
using IoWorker;
using ESLReminder.Modules.WarnModules;
using Settings = GlobalSettings.Properties.Settings;

namespace ESLReminder
{
    public class Reminder : IReminderInterface
    {
        public void AddWarning(WarnEvents e, int timeout)
        {
            m_WarnModules.Warn(e, timeout);
        }
        public void RemoveWarning(WarnEvents e)
        {
            m_WarnModules.UnWarn(e);
        }
        #region Events for Plugins and other Listeners
        public event GameStartedEventHandler GameStarted;
        public event GameEndedEventHandler GameEnded;
        #endregion
        #region Events
        public delegate void FilesCollectedEventHandler(Reminder sender, MatchArchive archive);
        public delegate void StatusTextChangedEventHandler(Reminder sender, String text);
        public delegate void OnExceptionEventHandler(Reminder sender, Exception ex);
        public event FilesCollectedEventHandler FilesCollected;
        public event StatusTextChangedEventHandler StatusTextChanged;
        #endregion
        Timer MainTimer = null;
        public ReminderModuleCollection m_Modules;
        public GameModuleCollection m_Games;

        MatchArchive m_Archive = null;
        public MatchArchive CurrentArchive
        {
            get
            {
                return m_Archive;
            }
        }

        public WarnModuleCollection m_WarnModules = null;

        Process[] RunningProcesses;

        protected void OnStatusTextChanged(string text)
        {
            StatusTextChangedEventHandler h = StatusTextChanged;
            if (h != null)
                AsyncHelper.FireAndForget(h, this, text);
        }

        protected void OnFilesCollected()
        {
            FilesCollectedEventHandler h = FilesCollected;
            if (h != null)
                AsyncHelper.FireAndForget(h, this, m_Archive);
        }

        protected void OnGameEnded(GameEndedEventArgs args)
        {
            GameEndedEventHandler h = GameEnded;
            if (h != null)
            {
                h(this, args);
            }
        }

        protected void OnGameStarted(GameStartedEventArgs args)
        {
            GameStartedEventHandler h = GameStarted;
            //TODO: pass on game information
            if (h != null)
                h(this, args);
                //AsyncHelper.FireAndForget(h, this, args);
        }
        
        private void GameProcessFound(GameModule game)
        {
            //deactivate MainTimer
            MainTimer.Change(Timeout.Infinite, Timeout.Infinite);

            Trace.TraceInformation("Game Process found!");
            game.Process.Exited += new EventHandler(GameProcess_Exited);
            try
            {
                game.Process.EnableRaisingEvents = true;
            }
            catch (Exception)
            {
                Trace.WriteLine("Could not access process");
                IgnoreWarnings();
                return;
            }
            m_Archive = new MatchArchive();

            //TODO: find another way to store this information
            /*m_Archive.Gamename = GameProcess.ProcessName;
            if (m_Archive.Gamename == "hl")
            {
                m_Archive.GameVersion = 1;
            }
            else if (m_Archive.Gamename == "hl2")
            {
                m_Archive.GameVersion = 2;
            }*/
            

            m_WarnModules.OnGameProcessChanged(game.Process);
            OnGameStarted(new GameStartedEventArgs(game));

            

            bool MatchOpen;
            try
            {
                OnStatusTextChanged("Checking for open match...");
                if (!WarnAlways)
                    Trace.TraceInformation("Checking for open match...");
                else
                    Trace.TraceInformation("WarnAlways is on.");

                //set open match always to true when "WarnAlways" is enabled
                MatchOpen = WarnAlways ? true : WebSynchronizer.isSomeMatchInReach(OffsetBefore, OffsetAfter, LoginCookie, MatchFilter);
            }
            catch (Exception e)
            {
                MatchOpen = true;
                Trace.TraceError(e.Message);
                Trace.TraceError(e.StackTrace);
            }

            if (MatchOpen)
            {
                if (!WarnAlways)
                    Trace.TraceInformation("Open match found!");
                OnStatusTextChanged("Monitoring game");
                Trace.TraceInformation("Reminder functions and hooks set up, monitoring game");
            }
            else
            {
                Trace.WriteLine("No open match, sleeping...");
                m_WarnModules.Ignore();
                OnStatusTextChanged("Waiting for HL/HL2 to exit");
            }


            Trace.WriteLine("Open match: " + MatchOpen.ToString());
        }

        private string m_GamePath;
        //TODO: remove this after refactoring
        /*private string GetModName()
        {
            string mod = string.Empty;

            try
            {
                Trace.WriteLine("Using WMI to get param info");
                //Use WMI to get the commandline-parameters hl/hl2 was started with
                ManagementObjectSearcher query = new ManagementObjectSearcher(String.Format("SELECT * FROM Win32_Process WHERE Name='{0}'", Path.GetFileName(GameProcess.MainModule.FileName)));
                ManagementObjectCollection queryCollection = query.Get();
                Debug.WriteLine(queryCollection.Count.ToString() + " instances found!");

                foreach (ManagementObject mo in queryCollection)
                {
                    //Console.WriteLine("PID [ {0} ] started '{1}' With commandline - '{2}'", mo["ProcessID"].ToString(), mo["CreationDate"].ToString(), mo["CommandLine"].ToString());
                    //if(mo["ProcessID"].ToString() == GameProcess.Id.ToString())
                    //TODO: don't print +rcon_password here
                    Trace.WriteLine(String.Format("PID [ {0} ] started '{1}' With commandline - '{2}'", mo["ProcessID"].ToString(), mo["CreationDate"].ToString(), mo["CommandLine"].ToString()));
                    Match m = Regex.Match(mo["CommandLine"].ToString(), @"-game (\S+)");
                    if (m.Success)
                    {
                        mod = m.Groups[1].Value;
                    }
                }
            }
            catch (System.Exception e)
            {
                Trace.WriteLine("Error while parsing commandline arguments: " + e.Message);
                Trace.WriteLine("Using fallback routine");
                mod = GameInterop.getModFromDir(Path.GetDirectoryName(GameProcess.MainModule.FileName));
            }
            return mod;
        }*/
        private GameModule m_CurrentGame = null;
        private void MainTimerTick(object dummy)
        {
            //TODO: tweak performance
            RunningProcesses = Process.GetProcesses();
            foreach (Process p in RunningProcesses)
            {
                GameModule game = m_Games.GetGameForProcess(p);
                if (game != null)
                {
                    GameProcessFound(game);
                    break;
                }
                else
                    p.Dispose();
            }
            // this method has higher mem usage at times (.NET does not seem to dispose the ProcessInfo instantly)
            // but this might be faster, depending on how windows treats our query
            /*m_CurrentGame = m_Games.GetRunningGame();
            if (m_CurrentGame != null)
            {
                GameProcessFound(m_CurrentGame);
            }*/
        }

        

        void GameProcess_Exited(object sender, EventArgs e)
        {
            Trace.WriteLine("Game process died!");
            
            if (StatusTextChanged != null)
            {
                Trace.WriteLine("Firing StatusTextChanged event (GameTerminated)");
                OnStatusTextChanged("Game terminated...");
            }

            if (StatusTextChanged != null)
            {
                Trace.WriteLine("Firing StatusTextChanged event (WaitingForAequitas)");
                OnStatusTextChanged("Waiting for aequitas to finish monitoring...");
            }
            //TODO: plan how to interact with the aequitas module for file collection purposes
            //      do we need to wait for it to finish? do we leave our mate behind?
            ReminderModule[] modules = m_Modules.PluginInstances;
            AequitasModule aeq = null;
            ConsoleModule con = null;
            foreach (ReminderModule m in modules)
            {
                if (m.GetType() == typeof(AequitasModule))
                {
                    aeq = m as AequitasModule;
                }
                else if (m.GetType() == typeof(ConsoleModule))
                {
                    con = m as ConsoleModule;
                }
            }
            if (aeq != null)
                aeq.WaitForAequitasFile();
            if (con != null)
                m_Archive.AutoRenameFiles
                    (
                    con.Events,
                    new MatchArchive.ParseSettings
                        (
                        Settings.Default.AequitasTitle,
                        Settings.Default.DemoVariationpercent
                        )
                    );
            /*try
            {
                //FIXME check if aequitas window is open
                if (AequitasWatcher != null && AequitasProcess != null)
                {
                    Trace.WriteLine("Waiting for aequitas to finish monitoring");
                    AequitasWatcher.WaitForChanged(WatcherChangeTypes.Created | WatcherChangeTypes.Changed, 60000);
                    Trace.WriteLine("Finished waiting for aequitas");
                }
            }catch{}*/
            //fire events
            OnGameEnded(new GameEndedEventArgs());

            //Start main-timer
            MainTimer.Change(0, m_MainTimerInterval);


            if (StatusTextChanged != null)
            {
                Trace.WriteLine("Firing StatusTextChanged event (waiting for gamelaunch)");
                OnStatusTextChanged("Waiting for gamelaunch...");
            }
            Trace.WriteLine("Firing FilesCollected event");
            OnFilesCollected();

            //Stop notifier
            Trace.WriteLine("Stopping WarnModules");
            m_WarnModules.Stop();
            Trace.WriteLine("WarnModules stopped");
            m_WarnModules.UnIgnore();
        }
        
        public void IgnoreWarnings()
        {
            //Stop notifier
            m_WarnModules.Ignore();
            
            OnStatusTextChanged("Ignoring warning until HL/HL2 exits...");
        }

        void hl2_Started(object sender, EventArgs e)
        {
            MainTimerTick(null);
        }

        void hl_Started(object sender, EventArgs e)
        {
            MainTimerTick(null);
        }

        

        #region Constructors
        public Reminder()
        {
            if (DebugEnabled)
            {
                if (File.Exists(Path.Combine(GlobalSettings.RuntimeSettings.AppDir, "debug.txt")))
                    File.Delete(Path.Combine(GlobalSettings.RuntimeSettings.AppDir, "debug.txt"));
                Trace.Listeners.Add(new TextWriterTraceListener("debug.txt"));
                Trace.AutoFlush = true;
            }

            m_Modules = new ReminderModuleCollection(this);
            bool loadSuccessful = m_Modules.LoadInstanciatedPlugins(Path.Combine(GlobalSettings.RuntimeSettings.SettingsDir, "Modules.xml"));
            Type[] types = m_Modules.PluginTypes;
            foreach (Type t in types)
            {
                if (m_Modules.GetPluginForType(t) == null)
                    m_Modules.AddNewPluginInstance(t);
            }

            m_WarnModules = new WarnModuleCollection(this);
            loadSuccessful = m_WarnModules.LoadInstanciatedPlugins(Path.Combine(GlobalSettings.RuntimeSettings.SettingsDir, "WarnModules.xml"));
            if (!loadSuccessful)
            {
                WarnModule m = m_WarnModules.AddNewPluginInstance(typeof(OSDWarnModule));
                m.AssignedEvents = WarnEvents.LowDiskspace | WarnEvents.NoAequitas | WarnEvents.NoReplay | WarnEvents.RestartReplay;
            }

            m_Games = new GameModuleCollection(this);
            Type[] gameTypes = m_Games.PluginTypes;
            foreach (Type t in gameTypes)
                m_Games.AddNewPluginInstance(t);

            //Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.BelowNormal;
            //m_WarnModules.TryLoad(Path.Combine(GlobalSettings.RuntimeSettings.SettingsDir, "default.qwm"));

            MainTimer = new Timer(new TimerCallback(MainTimerTick));
            
            //Start main-timer
            MainTimer.Change(0, m_MainTimerInterval);
        }

        ~Reminder()
        {
            //if (MainTimer != null)
                //MainTimer.Change(0, Timeout.Infinite);
            //if (m_Modules != null)
                //m_Modules.SaveInstanciatedPlugins(Path.Combine(GlobalSettings.RuntimeSettings.SettingsDir, "Modules.xml"));
            //if (m_WarnModules != null)
                //m_WarnModules.SaveInstanciatedPlugins(Path.Combine(GlobalSettings.RuntimeSettings.SettingsDir, "WarnModules.xml"));
        }
        #endregion

        #region Properties

        public long MainTimerInterval
        {
            get { return m_MainTimerInterval; }
            set
            {
                m_MainTimerInterval = value;
                MainTimer.Change(Timeout.Infinite, value);
            }
        }


        #endregion

        #region Settings

        public int AequitasWaitingTime = 5000;
        public TimeSpan OffsetBefore = new TimeSpan(0, 60, 0);
        public TimeSpan OffsetAfter = new TimeSpan(0, 120, 0);

        public bool DebugEnabled = true;
        public bool WarnAlways = true;
        public string LoginCookie = String.Empty;

        public StringCollection MatchFilter = new StringCollection();
        private long m_MainTimerInterval = 5000;
        
        
        
        #endregion
    }
}
